package moltencore.game;

import moltencore.game.bubble.BubbleRandomizer;
import spark.math.Vector2;

import java.util.ArrayList;
import java.util.Vector;
import java.util.LinkedList;
import java.util.Iterator;
import java.awt.*;

import moltencore.game.bubble.Bubble;
import spark.utility.IncrementalShortIdFactory;

public class Grid {
	/**
	 * By how much bubbles in indented rows are indented
	 */
	public static final float XINDENT = Bubble.RADIUS;
	public static final float XSTEP = Bubble.RADIUS * 2;

	/**
	 * Amount by which bubbles are interleaved on their y positions (y distance from center-to-center)
	 */
	@SuppressWarnings({"PointlessArithmeticExpression"})
	public static final float YSTEP = (XINDENT * 2) * 0.875f;

	private static final Vector2 PUSH_DIRECTION = new Vector2(0, -YSTEP);

	private Vector2 referencePoint = new Vector2();
	private boolean indentEvenRows;

	private LinkedList<Bubble> grid = new LinkedList<Bubble>();

	public Grid() {
		this(false);
	}

	public Grid(boolean indentEvenRows) {
		this.indentEvenRows = indentEvenRows;
	}

	/**
	 * Figures out the grid coordinates and according world coordinates for a bubble and attaches it to the grid.
	 *
	 * @param bubble Bubble to attach
	 */
	public void snapToGrid(Bubble bubble) {
		//figure out the grid coordinates for the bubble and the according world coordinates
		bubble.getGridPosition().y = fixGridY(bubble.getPosition().y);
		bubble.getGridPosition().x = fixGridX(bubble.getPosition().x, isRowIndented(bubble.getGridPosition().y));
		//apply them
		bubble.getPosition().copy(getWorldCoordinates(bubble.getGridPosition().x, bubble.getGridPosition().y));

		attach(bubble);
	}

	/**
	 * Attaches a bubble to a desired grid coordinate fixing the bubbles world coordinates
	 * accordingly, the old word coordinates are discarded and the new ones applied instead.
	 *
	 * @param bubble        Bubble to attach
	 * @param gridPositionX Valid and empty grid coordinate
	 * @param gridPositionY Valid and empty grid coordinate
	 */
	public void snapToGrid(Bubble bubble, int gridPositionX, int gridPositionY) {
		Vector2 worldCoordinates = getWorldCoordinates(gridPositionX, gridPositionY);

		bubble.getGridPosition().x = gridPositionX;
		bubble.getGridPosition().y = gridPositionY;

		bubble.getPosition().copy(worldCoordinates);

		attach(bubble);
	}

	/**
	 * Pushes down all bubbles by one row and adds the pushBubbles to the grid.
	 * @param pushBubbles bubbles that are being pushed down
	 * @return  the vector in which the grid bubbles have been pushed
	 */
	public Vector2 pushRow(LinkedList<Bubble> pushBubbles) {
		//lower every bubble
		for(Bubble bubble : grid) {
			bubble.getGridPosition().y -= 1;
			bubble.getPosition().add(PUSH_DIRECTION);
			bubble.setAttachedToWorld(false);
		}
		indentEvenRows = !indentEvenRows;
		//attach the push bubbles
		for(Bubble pushBubble : pushBubbles) {
			pushBubble.setAttachedToWorld(true);
			snapToGrid(pushBubble, pushBubble.getGridPosition().x, pushBubble.getGridPosition().y);
		}
		return PUSH_DIRECTION;
	}

	/**
	 * Pushes down all bubbles by one.
	 * @return true if after the push there is one or more bubbles in the critical area.
	 */
	public boolean pushCeiling() {
		for(Bubble bubble : grid) {
			bubble.getGridPosition().y += 1;
			bubble.getPosition().y -= YSTEP;
		}
		//todo
		return false;
	}
	/**
	 * Attaches a prepared bubble to the grid and links it to its neighbours.
	 * Prepared means that the bubbles grid and world coordinates have already been set correctly.
	 *
	 * @param bubble A bubble of which the grid and world coordinates have already been set
	 */
	private void attach(Bubble bubble) {
		//since we fix the bubble to the grid it has no more velocity
		bubble.getVelocity().x = bubble.getVelocity().y = 0;
		grid.add(bubble);

		//attach the bubble to its neigbours
		Point[] neighbours = new Point[6];
		//LeftBottom & RightBottom
		neighbours[Bubble.LEFT_BOT] = new Point(bubble.getGridPosition().x, bubble.getGridPosition().y - 1);
		neighbours[Bubble.RIGHT_BOT] = new Point(bubble.getGridPosition().x, bubble.getGridPosition().y - 1);
		//LeftTop & RightTop
		neighbours[Bubble.LEFT_TOP] = new Point(bubble.getGridPosition().x, bubble.getGridPosition().y + 1);
		neighbours[Bubble.RIGHT_TOP] = new Point(bubble.getGridPosition().x, bubble.getGridPosition().y + 1);
		//Left & Right
		neighbours[Bubble.LEFT] = new Point(bubble.getGridPosition().x - 1, bubble.getGridPosition().y);
		neighbours[Bubble.RIGHT] = new Point(bubble.getGridPosition().x + 1, bubble.getGridPosition().y);
		if(isRowIndented(bubble.getGridPosition().y)) {
			neighbours[Bubble.RIGHT_BOT].x += 1;
			neighbours[Bubble.RIGHT_TOP].x += 1;
		}
		else {
			neighbours[Bubble.LEFT_BOT].x -= 1;
			neighbours[Bubble.LEFT_TOP].x -= 1;
		}


        for(Bubble b : grid)
        {
	        int x = b.getGridPosition().x;
            int y = b.getGridPosition().y;
	        for(int neighbour = 0; neighbour < neighbours.length; neighbour++) {
				if(y == neighbours[neighbour].y && x == neighbours[neighbour].x) {
					bubble.attach(b, neighbour);
					break;
				}
	        }
        }
	}

	public Vector2 getWorldCoordinates(int x, int y) {
		Vector2 worldCoordinates = new Vector2();
		worldCoordinates.y = referencePoint.y + y * YSTEP;
		worldCoordinates.x = (isRowIndented(y) ? XINDENT : 0) + referencePoint.x + x * XSTEP;
		return worldCoordinates;
	}

	private int fixGridY(float worldY) {
		float distanceY = (worldY - referencePoint.y) / YSTEP;
		float positioning = distanceY - (int)distanceY;
		int gridPositionY = (int)distanceY;
		//bubble is one YSTEP lower than gridPositionY; this will be the case most of the time
		if(positioning <= -0.5) {
			gridPositionY -= 1;
		}
		//bubble is one YSTEP above gridPositionY
		else if(positioning > 0.5) {
			gridPositionY += 1;
		}
		/*// bubble is at gridPositionY
		else {
		}*/
		return gridPositionY;
	}

	private int fixGridX(float worldX, boolean indented) {
		@SuppressWarnings({"PointlessArithmeticExpression"})
		float distanceX = (worldX - referencePoint.x - (indented ? XINDENT : 0)) / XSTEP;
		float positioning = distanceX - (int)distanceX;
		int gridPositionX = (int)distanceX;
		//bubble is one XINDENT left of gridPositionX
		if(positioning < -0.5) {
			gridPositionX -= 1;
		}
		//bubble is one XINDENT right of gridPositionX
		else if(positioning >= 0.5) {
			gridPositionX += 1;
		}
		/*// bubble is at gridPositionX
		else {
		}*/
		return gridPositionX;
	}

	public boolean isRowIndented(int gridY) {
		return ((gridY % 2) == 0) ? indentEvenRows : !indentEvenRows;
	}

	public Bubble clearBubble(int x, int y) {
		for(Iterator<Bubble> i = grid.iterator(); i.hasNext();) {
			Bubble b = i.next();
			if(b.getGridPosition().x == x && b.getGridPosition().y == y) {
				i.remove();
				return b;
			}
		}
		return null;
	}

	public Bubble clearBubble(Bubble bubble) {
		for(Iterator<Bubble> i = grid.iterator(); i.hasNext();) {
			Bubble b = i.next();
			if(b.equals(bubble)) {
				i.remove();
				return b;
			}
		}
		return null;
	}

	public Bubble getBubble(int x, int y) {
		for(Iterator<Bubble> i = grid.iterator(); i.hasNext();) {
			Bubble b = i.next();
			if(b.getGridPosition().x == x && b.getGridPosition().y == y) {
				return b;
			}
		}
		return null;
	}

	public LinkedList<Bubble> getBubbles() {
		return grid;
	}
}
